package utils

import (
	"context"
	"crypto/rand"
	"crypto/subtle"
	"encoding/hex"
	"io"
	"os"
	"os/signal"
	"sync"

	"github.com/oklog/run"
	"github.com/sirupsen/logrus"
)

// InterruptContext returns a context which is
// cancelled on OS Interrupt
func InterruptContext() context.Context {
	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		sig := make(chan os.Signal, 1)
		signal.Notify(sig, os.Interrupt) //windows compatible?
		<-sig
		signal.Stop(sig)
		cancel()
	}()
	return ctx
}

// Deduplicate deduplicates list of strings
func Deduplicate(in []string) []string {
	if len(in) == 0 {
		return in
	}
	out := make([]string, 0, len(in))
	seen := make(map[string]bool, len(in))
	for _, val := range in {
		if _, ok := seen[val]; !ok {
			out = append(out, val)
			seen[val] = true
		}
	}
	return out
}

func SyncPipe(ctx context.Context, src io.ReadWriteCloser, dst io.ReadWriteCloser) (int64, int64) {
	var sent, received int64
	var o sync.Once
	closeAll := func() {
		if src != nil {
			src.Close()
		}
		if dst != nil {
			dst.Close()
		}
	}
	var g run.Group
	{
		g.Add(func() error {
			<-ctx.Done()
			return nil
		}, func(err error) {
			o.Do(closeAll)
		})
	}
	{
		g.Add(func() error {
			var err error
			if dst != nil && src != nil {
				received, err = io.Copy(src, dst)
			}
			return err
		}, func(err error) {
			o.Do(closeAll)
		})
	}
	{
		g.Add(func() error {
			var err error
			if dst != nil && src != nil {
				sent, err = io.Copy(dst, src)
			}
			return err
		}, func(err error) {
			o.Do(closeAll)
		})
	}

	if err := g.Run(); err != nil {
		logrus.WithError(err).Error("error pipe io")
	}
	return sent, received
}

func BytesEqual(a, b []byte) bool {
	return (len(a) == len(b) && subtle.ConstantTimeCompare(a, b) == 1)
}

func TempFileName(prefix, suffix string) string {
	randBytes := make([]byte, 5)
	rand.Read(randBytes)
	return prefix + hex.EncodeToString(randBytes) + suffix
}

func SubString(input string, start int, length int) string {
	asRunes := []rune(input)

	if start >= len(asRunes) {
		return ""
	}

	if start+length > len(asRunes) {
		length = len(asRunes) - start
	}

	res := string(asRunes[start : start+length])
	if len(input) > length {
		res = res + "..."
	}

	return res
}

func FileExists(filename string) bool {
	info, err := os.Stat(filename)
	if os.IsNotExist(err) {
		return false
	}
	return !info.IsDir()
}
